<?php
set_time_limit(0);
//国内版
class ext_onedrive extends App{
	public $client_id = "";
	public $client_secret = "";
	public $redirect_uri = "https://oneindex.github.io/";
	public $api_url = 'https://bilnn1-my.sharepoint.cn/';
	public $oauth_url = 'https://login.chinacloudapi.cn/common/oauth2';
	public $mail = "https://bilnn1-my.sharepoint.cn/:u:/g/personal/admin_bilnn1_partner_onmschina_cn/";

	//获取授权
	public function auth(){
		echo "<a href='https://portal.azure.cn' target='_blank'>第一步：点击获取client_id和client_secret</a><br><br>";
		echo "<a href='".$this->authorize_url()."'>第二部：绑定账号</a>";
	}

	//验证URL，浏览器访问、授权
	public function authorize_url(){
		$redirect_uri = $this->redirect_uri;
		$client_id = $this->client_id;
		$redirect_uri = $this->redirect_uri;
		$url = $this->oauth_url."/authorize?response_type=code&client_id={$client_id}&redirect_uri={$redirect_uri}";
		$url .= '&state='.urlencode('http://'.$_SERVER['HTTP_HOST'].'/');
		return $url;
	}

	//使用 $code, 获取 $refresh_token
	public function authorize($code = "", $resource_id="00000003-0000-0ff1-ce00-000000000000"){
		$client_id = $this->client_id;
		$client_secret = $this->client_secret;
		$redirect_uri = $this->redirect_uri;

		$url = $this->oauth_url."/token";
		$post_data = "client_id={$client_id}&redirect_uri={$redirect_uri}&client_secret={$client_secret}&code={$code}&grant_type=authorization_code&resource={$resource_id}";
		fetch::$headers = "Content-Type: application/x-www-form-urlencoded";
		$resp = fetch::post($url, $post_data);
		$data = json_decode($resp->content, true);
		return $data;
	}

	//使用 $refresh_token，获取 $access_token
	public function get_token($refresh_token){
		$client_id = $this->client_id;
		$client_secret = $this->client_secret;
		$redirect_uri = $this->redirect_uri;
		$resource_id = $this->api_url;
		$url = $this->oauth_url."/token";
		$post_data = "client_id={$client_id}&redirect_uri={$redirect_uri}&client_secret={$client_secret}&refresh_token={$refresh_token}&grant_type=refresh_token&resource={$resource_id}";
		fetch::$headers = "Content-Type: application/x-www-form-urlencoded";
		$resp = fetch::post($url, $post_data);
		$data = json_decode($resp->content, true);
		return $data;
	}

	//获取 $access_token, 带缓存
	public function access_token(){
		$token = $this->access('@token');
		if($token['expires_on'] > time()+600){
			return $token['access_token'];
		}else{
			if (empty($token) || empty($token['refresh_token'])) {
    			$refresh_token = $this->access('refresh_token');
			} else {
    			$refresh_token = $token['refresh_token'];
			}
			$token = $this->get_token($refresh_token);
			if(!empty($token['refresh_token'])){
				$token['expires_on'] = time()+ $token['expires_in'];
				$this->access('@token', $token);
				return $token['access_token'];
			}
		}
		return "";
	}

	//字节转换
	public function human_filesize($size, $precision = 1) {
		for($i = 0; ($size / 1024) > 1; $i++, $size /= 1024) {}
		return round($size, $precision).['B','kB','MB','GB','TB','PB','EB','ZB','YB'][$i];
	}

	// 生成一个request，带token
	public function request($path="/", $query=""){
		$path = $this->urlencode($path);
		$path = empty($path)?'/':":/{$path}:/";
		$token = $this->access_token();
		$request['headers'] = "Authorization: bearer {$token}".PHP_EOL."Content-Type: application/json".PHP_EOL;
		$request['url'] = $this->api_url."_api/v2.0/me/drive/root".$path.$query;
		return $request;
	}

	
	//返回目录信息
	public function dir($path="/"){
		$request = $this->request($path, "children?select=name,size,folder,@microsoft.graph.downloadUrl,lastModifiedDateTime");
		$items = array();
		$this->dir_next_page($request, $items);
		//不在列表显示的文件夹
		$hide_list = explode(PHP_EOL,$this->access('onedrive_hide'));
		if(is_array($hide_list) && count($hide_list)>0){
			foreach($hide_list as $hide_dir){
				foreach($items as $key=>$_array){
					$buf = trim($hide_dir);
					if($buf && stristr($key, $buf))unset($items[$key]);
				}
			}
		}
		return $items;
	}

	//通过分页获取页面所有item
	public function dir_next_page($request, &$items, $retry=0){
		$resp = fetch::get($request);
		$data = json_decode($resp->content, true);
		if(empty($data) && $retry < 3){
			$retry += 1;
			return $this->dir_next_page($request, $items, $retry);
		}
		
		foreach((array)$data['value'] as $item){
			//var_dump($item);
			$downloadUrl = "";
			if (!empty($item['@content.downloadUrl'])){
				$downloadUrl = $item['@content.downloadUrl'];
			}
			$items[$item['name']] = array(
				'name'=>$item['name'],
				'size'=>$item['size'],
				'lastModifiedDateTime'=>strtotime($item['lastModifiedDateTime']),
				'downloadUrl'=>$downloadUrl,
				'folder'=>empty($item['folder'])?false:true
			);
		}

		if(!empty($data['@odata.nextLink'])){
			$request = $this->request();
			$request['url'] = $data['@odata.nextLink'];
			return $this->dir_next_page($request, $items);
		}
	}

	//文件下载链接
	public function down_url($path){
		$request = $this->request($path);
		$resp = fetch::get($request);
		$data = json_decode($resp->content, true);
		if (empty($data['@content.downloadUrl'])) return "";
		return $data['@content.downloadUrl'];
	}

	//文件缩略图链接
	public function thumbnail($path,$size='large'){
		$request = $this->request($path,"thumbnails/0?select={$size}");
		$resp = fetch::get($request);
		$data = json_decode($resp->content, true);
		return @$data[$size]['url'];
	}

	//分享
	public function share($path){
		$request = $this->request($path,"createLink");
		$post_data['type'] = 'view';
		$post_data['scope'] = 'anonymous';
		$resp = fetch::post($request, json_encode($post_data));
		$data = json_decode($resp->content, true);
		return $data;
	}

	//删除文件
	public function del_file($path){
		$request = $this->request($path);
		$resp = fetch::delete($request);
		return true;
	}

	//文件上传函数
	public function upload($local_path,$path){
		$data = array();
		$local_path = realpath($local_path);
		if(!file_exists($local_path)){
			$data['code'] = 1;
			$data['msg'] = "本地文件不存在";
			return $data;
		}
		$filesize = $this->_filesize($local_path);
		/*if($filesize < 3145728){
			$content = file_get_contents($local_path);
			$request = $this->request($path,"content");
			$request['post_data'] = $content;
			$resp = fetch::put($request);
			$data = @json_decode($resp->content, true);

			$data = array();
			$data['code'] = 0;
			$data['msg'] = "方式1上传成功";
			$va = explode("\\", $local_path);
			$data['backdata']['name'] = end($va);
			$data['backdata']['remote_path'] = str_replace($data['backdata'], "", $path);
			$data['backdata']['size'] = $filesize;
		}else{*/
			$data = $this->upload_large_file($local_path, $path);
		/*}*/
		return $data;
	}

	//大文件分块上传
	public function upload_large_file($localfile, $remotepath ,$upload = array()){
		fetch::init([CURLOPT_TIMEOUT=>200]);
		if ($upload) $info = $upload[$remotepath];
		if(empty($info['url'])){
			$data = $this->create_upload_session($remotepath);
			if(!empty($data['uploadUrl'])){
				$info['url'] = $data['uploadUrl'];
				$info['localfile'] = $localfile;
				$info['remotepath'] = $remotepath;
				$info['filesize'] = $this->_filesize($localfile);
				$info['offset'] = 0;
				$info['length'] = 327680;
				$info['update_time'] = time();
				$upload[$remotepath] = $info;
			}elseif ( $data === false ){
				$data['code'] = 2;
				$data['msg'] = "文件已存在";
				return $data;
			}
		}
		if(empty($info['url'])){
			$data['code'] = 3;
			$data['msg'] = "创建上传会话失败";
			return $data;
		}
		$begin_time = microtime(true);
		$data = $this->upload_session($info['url'], $info['localfile'], $info['offset'], $info['length']);
		if(!empty($data['nextExpectedRanges'])){
			$upload_time = microtime(true) - $begin_time;
			$info['speed'] = $info['length']/$upload_time;
			$info['length'] = intval($info['length']/$upload_time/32768*2)*327680;
			$info['length'] = ($info['length']>104857600)?104857600:$info['length'];
			list($offset, $filesize) = explode('-',$data['nextExpectedRanges'][0]);
			$info['offset'] = $offset;
			$info['update_time'] = time();
			$upload[$remotepath] = $info;
		}elseif(!empty($data['@content.downloadUrl']) || !empty($data['id'])){
			$data = array();
			$data['code'] = 0;
			$data['msg'] = "方式2上传成功";
			$va = explode("\\", $localfile);
			$data['backdata']['name'] = end($va);
			$data['backdata']['remote_path'] = str_replace($data['backdata'], "", $remotepath);
			$data['backdata']['size'] = $this->_filesize($localfile);
			return $data;
		}else{
			$data = $this->upload_session_status($info['url']);
			if(empty($data)|| $info['length']<100){
				$this->delete_upload_session($info['url']);
				unset($upload[$remotepath]);
			}elseif(!empty($data['nextExpectedRanges'])){
				list($offset, $filesize) = explode('-',$data['nextExpectedRanges'][0]);
				$info['offset'] = $offset;
				$info['length'] = $info['length']/1.5;
				$upload[$remotepath] = $info;
			}
		}
		
		return $this->upload_large_file($localfile, $remotepath,$upload);
	}

	//上传文件夹
	public function upload_folder($localfolder, $remotefolder='/'){
		$localfolder = realpath($localfolder);
		$remotefolder = $this->get_absolute_path($remotefolder);
		return $this->folder2upload($localfolder,$remotefolder);
	}

	public function folder2upload($localfolder, $remotefolder){
		$files = scandir($localfolder);
		$path_list = array();
		foreach ($files as $file) {
			if ($file == '.' || $file == '..') {
				continue;
			}
			if (is_dir($localfolder . '/' . $file)) {
		        $this->folder2upload($localfolder . '/' . $file, $remotefolder.$file.'/');
		    }else{
			    $localfile = realpath($localfolder . '/' . $file);
			    $remotefile = $remotefolder.$file;
			    $path = $this->upload($localfile, $remotefile);
			    if ($path['code']==0){
			    	$path_list[] = $path['backdata'];
			    }
		    }
		}
		return $path_list;
	}

	public function create_upload_session($path){
		$request = $this->request($path, 'createUploadSession');
		$request['post_data'] = '{"item": {"@microsoft.graph.conflictBehavior": "fail"}}';
		$token = $this->access_token();
		$resp = fetch::post($request);
		$data = json_decode($resp->content, true);
		if($resp->http_code == 409){
			return false;
		}
		return $data;
	}

	public function upload_session($url, $file, $offset, $length=10240){
		$token = $this->access_token();
		$file_size = $this->_filesize($file);
		$content_length = (($offset+$length)>$file_size)?($file_size-$offset):$length;
		$end = $offset+$content_length-1;
		$post_data = $this->file_content($file, $offset, $length);

		$request['url'] = $url;
		$request['curl_opt']=[CURLOPT_TIMEOUT=>360];
		$request['headers'] = "Authorization: bearer {$token}".PHP_EOL;
		$request['headers'] .= "Content-Length: {$content_length}".PHP_EOL;
		$request['headers'] .= "Content-Range: bytes {$offset}-{$end}/{$file_size}";
		$request['post_data'] = $post_data;
		$resp = fetch::put($request);
		$data = json_decode($resp->content, true);
		return $data;
	}

	public function upload_session_status($url){
		$token = $this->access_token();
		fetch::$headers = "Authorization: bearer {$token}".PHP_EOL."Content-Type: application/json".PHP_EOL;
		$resp = fetch::get($url);
		$data = json_decode($resp->content, true);
		return $data;
	}

	public function delete_upload_session($url){
		$token = $this->access_token();
		fetch::$headers = "Authorization: bearer {$token}".PHP_EOL."Content-Type: application/json".PHP_EOL;
		$resp = fetch::delete($url);
		$data = json_decode($resp->content, true);
		return $data;
	}

	public function file_content($file, $offset, $length){
		$handler = fopen($file, "rb") OR die('获取文件内容失败');
		fseek($handler, $offset);
		return fread($handler, $length);
	}

	public function get_absolute_path($path) {
	    $path = str_replace(array('/', '\\', '//'), '/', $path);
	    $parts = array_filter(explode('/', $path), 'strlen');
	    $absolutes = array();
	    foreach ($parts as $part) {
	        if ('.' == $part) continue;
	        if ('..' == $part) {
	            array_pop($absolutes);
	        } else {
	            $absolutes[] = $part;
	        }
	    }
	    return str_replace('//','/','/'.implode('/', $absolutes).'/');
	}

	public function _filesize($path){
	    if (!file_exists($path))
	        return false;
	    $size = filesize($path);
	    if (!($file = fopen($path, 'rb')))
	        return false;
	    if ($size >= 0){
	        if (fseek($file, 0, SEEK_END) === 0){
	            fclose($file);
	            return $size;
	        }
	    }
	    $size = PHP_INT_MAX - 1;
	    if (fseek($file, PHP_INT_MAX - 1) !== 0){
	        fclose($file);
	        return false;
	    }
	    $length = 1024 * 1024;
	    while (!feof($file)){//Read the file until end
	        $read = fread($file, $length);
	        $size = bcadd($size, $length);
	    }
	    $size = bcsub($size, $length);
	    $size = bcadd($size, strlen($read));
	    fclose($file);
	    return $size;
	}

	public function urlencode($path){
		$paths = array();
		foreach(explode('/', $path) as $k=>$v){
			if(empty(!$v)){
				$paths[] = rawurlencode($v);
			}
		}
		if (!$paths) $paths = array();
		return @join('/',$paths);
	}
		
}

class fetch {
	public static $headers = "User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36";
	public static $cookies;
	public static $curl_opt;
	public static $proxy;

	public static $max_connect = 10;

	public static function init($opt = array()) {
		self::$curl_opt = array(
			CURLOPT_RETURNTRANSFER => 1, //true, $head 有请求的返回值
			CURLOPT_BINARYTRANSFER => true, //返回原生的Raw输出
			CURLOPT_HEADER => true, //启用时会将头文件的信息作为数据流输出。
			CURLOPT_FAILONERROR => true, //显示HTTP状态码，默认行为是忽略编号小于等于400的HTTP信息。
			CURLOPT_AUTOREFERER => true, //当根据Location:重定向时，自动设置header中的Referer:信息。
			CURLOPT_FOLLOWLOCATION => false, //跳转
			CURLOPT_CONNECTTIMEOUT => 3, //在发起连接前等待的时间，如果设置为0，则无限等待。
			CURLOPT_TIMEOUT => 5, //设置cURL允许执行的最长秒数。
			CURLOPT_ENCODING => 'gzip,deflate',
			CURLOPT_SSL_VERIFYHOST => false,
			CURLOPT_SSL_VERIFYPEER => false,
		);
		foreach ($opt as $k => $v) {
			self::$curl_opt[$k] = $v;
		}
	}

	/**
	 * fetch::get('http://www.google.com/');
	 * fetch::post('http://www.google.com/', array('name'=>'foo'));
	 */
	public static function __callstatic($method, $args) {
		if (is_null(self::$curl_opt)) {
			self::init();
		}
		if(empty($args[1])){
			$args[1] = "";
		}
		if(empty($args[2])){
			$args[2] = "";
		}
		@list($request, $post_data, $callback) = $args;
		if (is_callable($post_data)) {
			$callback = $post_data;
			$post_data = null;
		}

		//single_curl
		if (is_string($request) || !empty($request['url'])) {
			$request = self::bulid_request($request, $method, $post_data, $callback);
			return self::single_curl($request);
		} elseif (is_array($request)) {
			//rolling_curl
			foreach ($request as $k => $r) {
				$requests[$k] = self::bulid_request($r, $method, $post_data, $callback);
			}
			return self::rolling_curl($requests);
		}
	}

	private static function bulid_request($request, $method = 'GET', $post_data = null, $callback = null) {
		//url
		if (is_string($request)) {
			$request = array('url' => $request);
		}
		empty($request['method']) && $request['method'] = $method;
		empty($request['post_data']) && $request['post_data'] = $post_data;
		empty($request['callback']) && $request['callback'] = $callback;
		return $request;
	}

	private static function bulid_ch(&$request) {
		// url
		$ch = curl_init($request['url']);
		// curl_opt
		$curl_opt = empty($request['curl_opt']) ? array() : $request['curl_opt'];
		$curl_opt = $curl_opt + (array) self::$curl_opt;
		// method
		$curl_opt[CURLOPT_CUSTOMREQUEST] = strtoupper($request['method']);
		// post_data
		if (!empty($request['post_data'])) {
			$curl_opt[CURLOPT_POST] = true;
			$curl_opt[CURLOPT_POSTFIELDS] = $request['post_data'];
		}
		// header
		if (!empty($request['headers'])){
			$headers = @self::bulid_request_header($request['headers'], $cookies);
			$curl_opt[CURLOPT_HTTPHEADER] = $headers;
		}

		// cookies
		if (!empty($request['cookies'])){
			$request['cookies'] = empty($request['cookies']) ? fetch::$cookies : $request['cookies'];
			$cookies = empty($request['cookies']) ? $cookies : self::cookies_arr2str($request['cookies']);
			if (!empty($cookies)) {
				$curl_opt[CURLOPT_COOKIE] = $cookies;
			}
		}

		//proxy
		$proxy = empty($request['proxy']) ? self::$proxy : $request['proxy'];
		if (!empty($proxy)) {
			$curl_opt[CURLOPT_PROXY] = $proxy;
		}

		//setopt
		curl_setopt_array($ch, $curl_opt);

		$request['curl_opt'] = $curl_opt;
		$request['ch'] = $ch;

		return $ch;
	}

	private static function response($raw, $ch) {
		$response = (object) curl_getinfo($ch);
		$response->raw = $raw;
		//$raw = fetch::iconv($raw, $response->content_type);
		$response->headers = substr($raw, 0, $response->header_size);
		$response->cookies = fetch::get_respone_cookies($response->headers);
		fetch::$cookies = array_merge((array) fetch::$cookies, $response->cookies);
		$response->content = substr($raw, $response->header_size);
		return $response;
	}

	private static function single_curl($request) {
		$ch = self::bulid_ch($request);
		$raw = curl_exec($ch);
		$response = self::response($raw, $ch);
		curl_close($ch);
		if (is_callable($request['callback'])) {
			call_user_func($request['callback'], $response, $request);
		}
		return $response;
	}

	private static function rolling_curl($requests) {
		$master = curl_multi_init();
		$map = array();
		// start the first batch of requests
		do {
			$k = key($requests);
			$request = current($requests);
			next($requests);
			$ch = self::bulid_ch($request);
			curl_multi_add_handle($master, $ch);
			$key = (string) $ch;
			$map[$key] = array($k, $request['callback']);
		} while (count($map) < self::$max_connect && count($map) < count($requests));

		do {
			while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
			if ($execrun != CURLM_OK) {
				break;
			}

			// a request was just completed -- find out which one
			while ($done = curl_multi_info_read($master)) {
				$key = (string) $done['handle'];

				list($k, $callback) = $map[$key];

				// get the info and content returned on the request
				$raw = curl_multi_getcontent($done['handle']);
				$response = self::response($raw, $done['handle']);
				$responses[$k] = $response;

				// send the return values to the callback function.
				if (is_callable($callback)) {
					$key = (string) $done['handle'];
					unset($map[$key]);
					call_user_func($callback, $response, $requests[$k], $k);
				}

				// start a new request (it's important to do this before removing the old one)
				$k = key($requests);
				if (!empty($k)) {
					$k = key($requests);
					$request = current($requests);
					next($requests);
					$ch = self::bulid_ch($request);
					curl_multi_add_handle($master, $ch);
					$key = (string) $ch;
					$map[$key] = array($k, $request['callback']);
					curl_multi_exec($master, $running);
				}

				// remove the curl handle that just completed
				curl_multi_remove_handle($master, $done['handle']);
			}

			// Block for data in / output; error handling is done by curl_multi_exec
			if ($running) {
				curl_multi_select($master, 10);
			}

		} while ($running);

		return $responses;
	}

	private static function bulid_request_header($headers, &$cookies) {
		if (is_array($headers)) {
			$headers = join(PHP_EOL, $headers);
		}
		if (is_array(self::$headers)) {
			self::$headers = join(PHP_EOL, self::$headers);
		}
		$headers = self::$headers.PHP_EOL .$headers;

		foreach (explode(PHP_EOL, $headers) as $k => $v) {
			$va = explode(':', $v, 2);
			if (empty($va[1])) continue;
			@list($k, $v) = array($va[0],$va[1]);
			if (empty($k) || empty($v)) {
				continue;
			}
			$k = implode('-', array_map('ucfirst', explode('-', $k)));
			$tmp[$k] = $v;
		}

		foreach ((array) $tmp as $k => $v) {
			if ($k == 'Cookie') {
				$cookies = $v;
			} else {
				$return[] = $k . ':' . $v;
			}
		}
		return (array) $return;
	}

	public static function iconv(&$raw, $content_type) {
		@list($tmp, $charset) = explode('CHARSET=', strtoupper($content_type));

		if (empty($charset) && stripos($content_type, 'html') > 0) {
			preg_match('@\<meta.+?charset=([\w]+)[\'|\"]@i', $raw, $matches);
			$charset = empty($matches[1]) ? null : $matches[1];
		}

		return empty($charset) ? $raw : iconv($charset, "UTF-8//IGNORE", $raw);
	}

	public static function get_respone_cookies($raw) {
		$cookies = array();
		$lines = array();
		if(strpos($raw, PHP_EOL) != false){
		    $lines = explode(PHP_EOL, $raw);
		}elseif(strpos($raw, "\r\n") != false){
		    $lines = explode("\r\n", $raw);
		}elseif(strpos($raw, '\r\n') != false){
		    $lines = explode('\r\n', $raw);
		}
		
		foreach ((array)$lines as $line) {
			if (substr($line, 0, 11) == 'Set-Cookie:') {
				list($k, $v) = explode('=', substr($line, 11), 2);
				list($v, $tmp) = explode(';', $v);
				$cookies[trim($k)] = trim($v);
			}
		}
		return $cookies;
	}

	public static function cookies_arr2str($arr) {
		$str = "";
		foreach ((array) $arr as $k => $v) {
			$str .= $k . "=" . $v . "; ";
		}
		return $str;
	}
}